Script Loader: Defer single-page admin init until DOMContentLoaded (Trac #65103)#11611
Open
itzmekhokan wants to merge 2 commits intoWordPress:trunkfrom
Open
Script Loader: Defer single-page admin init until DOMContentLoaded (Trac #65103)#11611itzmekhokan wants to merge 2 commits intoWordPress:trunkfrom
itzmekhokan wants to merge 2 commits intoWordPress:trunkfrom
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
The Connectors and Font Library admin screens register a `-prerequisites`
classic-script handle with an empty `src` and attach an inline script that
does `import("@wordpress/boot").then(mod => mod.init(...))`. Because the
handle has no `src`, only the inline is printed, and it runs as a classic
script the moment the parser reaches it.
On fast CDN-fronted hosts (WordPress VIP, CloudFront, etc.) in Chrome,
`@wordpress/boot` is already `<link rel="modulepreload">`-ed, so the
dynamic import resolves and the module evaluates before the parser has
reached the classic deps it relies on (`wp-private-apis`, `wp-components`,
`wp-theme`). At its top-level `@wordpress/boot` reads
`window.wp.theme.privateApis`, which is still undefined at that point,
causing `unlock(undefined)` to throw "Cannot unlock an undefined object".
The mount element stays empty and `initSinglePage` / `init` never runs.
Wrap the dynamic import in `DOMContentLoaded`. The HTML spec guarantees
`DOMContentLoaded` fires only after every parser-blocking classic
`<script>` has executed, so all required globals are populated before
`@wordpress/boot` evaluates. A `document.readyState` guard preserves the
existing behavior when the inline is evaluated after DOM ready (e.g.
injected late).
Applied to all four auto-generated files that share the pattern:
* src/wp-includes/build/pages/options-connectors/page.php
* src/wp-includes/build/pages/options-connectors/page-wp-admin.php
* src/wp-includes/build/pages/font-library/page.php
* src/wp-includes/build/pages/font-library/page-wp-admin.php
Fixes #65103.
Made-with: Cursor
Follow-up to [65103] feedback. Adjust the multi-line `/* */` comments added for the DOMContentLoaded deferral so they match the WordPress PHP inline-documentation conventions: no DocBlock-style summary-then-blank separator (that structure is reserved for `/** */`), and use the short `See #65103.` form for Trac references instead of `See Trac #65103.` to match usages throughout core (`See #38883`, `See #40146`, etc.). See #65103. Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Trac ticket
https://core.trac.wordpress.org/ticket/65103
Summary
Fixes a race condition that prevents the Connectors (
/wp-admin/options-connectors.php) and Font Library admin screens from mounting in Chrome on fast-CDN-fronted hosts (reproducible on WordPress VIP). The failure surfaces as an empty mount div plusUncaught Error: Cannot unlock an undefined objectin the console.Root cause
The four auto-generated files below register a
-prerequisitesclassic-script handle with an emptysrcand attach an inline script that does a dynamic ESM import:Because the handle has no
src, only the inline is printed, and it runs as a classic<script>the instant the HTML parser reaches it.@wordpress/bootis<link rel="modulepreload">-ed in<head>. On a fast CDN the bundle is effectively free, so the dynamic import resolves and the module evaluates before the parser has reached the classic deps it relies on (wp-private-apis,wp-components,wp-theme).At its top-level
@wordpress/bootreadswindow.wp.theme.privateApis, which is stillundefinedat that point, sounlock(undefined)throws andinitSinglePage/initnever runs.Only Chrome + fast CDN reliably loses the race; Firefox/Safari schedule module eval slightly later, and local dev is slow enough that the classic deps usually finish first.
The fix (Option 1 from the ticket)
Wrap the dynamic import in
DOMContentLoaded. The HTML spec guaranteesDOMContentLoadedfires only after every parser-blocking classic<script>has executed, sowp.theme.privateApis(and the rest) are populated before@wordpress/bootevaluates. Adocument.readyState === "loading"guard preserves behavior when the inline is ever re-run after DOM ready (e.g. AJAX-injected contexts).Before:
After:
Files changed
All four auto-generated files that share this pattern:
src/wp-includes/build/pages/options-connectors/page.php(init)src/wp-includes/build/pages/options-connectors/page-wp-admin.php(initSinglePage)src/wp-includes/build/pages/font-library/page.php(init)src/wp-includes/build/pages/font-library/page-wp-admin.php(initSinglePage)Each file's
wp_add_inline_script(…)call is updated, plus an explanatory comment referencing Trac #65103. No registration logic, dependency lists, script module graph, or style registration is touched.Why not the other options from the ticket
wp_add_inline_script_module()API or ascript_loader_tagfilter — out of scope for a bugfix.initSinglePageintoloader.js) would change the public module contract, require a coordinated Gutenberg-side change, and migrate every downstream consumer of the pattern.Follow-up note for committers
These files are marked "Auto-generated by build process. Do not edit this file manually." The generator lives in Gutenberg's build pipeline. A search of the local Gutenberg checkout finds no matching template string, so the
wordpress-developcopies are effectively the current source of truth — but the Gutenberg-side template should receive the same change (or the next sync from Gutenberg will silently revert this). Happy to open a follow-up Gutenberg PR once this lands.Test plan
/wp-admin/options-connectors.phpand the Font Library admin screen. Confirm the app mounts and "Cannot unlock an undefined object" is not logged.document.readyStateguard: visit the page, then in the console re-evaluate the inline body and verifymod.init/mod.initSinglePageis still called exactly once (behavior preserved for late-arriving scripts).Related
wp_register_script('-prerequisites', '', …)+ inlineimport("@wordpress/boot")pattern has the same bug.